package com.gframework.context.spring;

import java.lang.annotation.Annotation;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * ApplicationContext操作工具类.<br/>
 * 本类同时也是一个SpringApplicationRunListener监听器。
 * <p>
 * 本类将会在 contextPrepared 阶段初始化，之后均可以使用。如果在这之前使用，则会出现NPE问题。
 * </p>
 * <p>
 * <strong>如果要使用本类，则需要引用springboot相关开发包</strong>
 * </p>
 * @since 2.0.0
 * @author Ghwolf
 *
 */
public class AppContext implements SpringApplicationRunListener{
	
	private static final Logger logger = LoggerFactory.getLogger(AppContext.class);
	
	/**
	 * 顶级容器对象，在web初始化的时候会取得该对象。
	 */
	private ApplicationContext application;
	
	/**
	 * springboot环境配置参数信息
	 */
	private Environment environment ;
	
	/**
	 * 一个可以用于动态注册bean和删除bean的bean工厂对象，这个对象是属于顶级容器的。
	 */
	private DefaultListableBeanFactory beanFactory ;
	
	/**
	 * 将本类对象设置到static成员中，可以让外部只通过调用静态方法就能过进行相关操作
	 */
	private static AppContext instance ;
	
	public AppContext(SpringApplication application,String[] args) {
		String gframeworkMainClassName = "com.gframework.GframeworkSpringBootStart";
		try {
			Class.forName(gframeworkMainClassName, false, Thread.currentThread().getContextClassLoader());
			boolean enabled = false ;
			for (Object o : application.getAllSources()) {
				if (o instanceof Class && ((Class<?>)o).getName().equals(gframeworkMainClassName)) {
					enabled = true ;
					break ;
				}
			}
			if (!enabled) {
				logger.info( "\n检测到您使用了gframework-core组件包，你可以使用：\n" + 
				"SpringApplication.run(new Class[]{\n" + 
				"                您的启动类.class,\n" + 
				"                GframeworkSpringBootStart.class\n" + 
				"        },args);\n" + 
				"这种方式来启动gframework web开发组件，如果您不需要，则可以无视！");
			}
		} catch(ClassNotFoundException e) {
			// Ignore
		}
		instance = this ;
	}

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.environment = environment;
	}
	
	
	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		this.application = context ;
		this.beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
	}
	
	
	/**
	 * 取得顶级ApplicationContext容器对象.<br>
	 * 顶级容器对象不能够取得servlet容器的bean
	 * 
	 * @return 顶级ApplicationContext容器对象
	 */
	public static ApplicationContext getApplication() {
		return AppContext.instance.application;
	}
	
	/**
	 * 获取 springboot环境配置参数信息
	 * @return springboot环境配置参数信息
	 */
	public static Environment getEnvironment(){
		return AppContext.instance.environment;
	}
	
	/**
	 * 获取bean工厂对象
	 */
	public static DefaultListableBeanFactory getBeanFactory(){
		return AppContext.instance.beanFactory;
	}
	
	/**
	 * 本方法可以从顶级容器中删除一个Prototy生命周期的bean并释放资源。
	 * 本方法只会从spring容器中删除bean，但是bean的定义对象不会被删除，即依然可以通过容器取得bean，只不过取得的是新的bean。
	 * 如果要删除bean的定义对象可以调用{@link #removeBeanDefinition(String)}方法
	 * 
	 * @param beanName bean的名称，如果不存在此bean或者bean名称和实例不匹配，则不做任何事情。
	 * @param instance bean实例
	 */
	public static void removePrototypeBean(String beanName,Object instance) {
		try {
			getBeanFactory().destroyBean(beanName,instance);
		} catch(NoSuchBeanDefinitionException e) {
			if (logger.isErrorEnabled()) {
				logger.error("删除一个bean发生错误！",e);
			}
			// Ignore
		}
	}
	
	/**
	 * 本方法可以从顶级容器中删除一个bean及其定义对象.<br>
	 * 注意，此方法会将bean和bean的定义对象全部删除。你将无法通过容器取得这个bean。
	 * 
	 * @param beanName bean的名称，如果不存在此bean的定义对象，则不做任何事情。
	 */
	public static void removeBeanDefinition(String beanName) {
		try {
			getBeanFactory().removeBeanDefinition(beanName);
		} catch(NoSuchBeanDefinitionException e) {
			if (logger.isErrorEnabled()) {
				logger.error("删除一个beanDefinition发生错误！",e);
			}
			// Ignore
		}
	}
	
	/**
	 * 注册一个新的【单例的】【默认名称的】bean到顶级spring容器中
	 * @param cls bean类型
	 * 
	 * @throws BeanDefinitionStoreException 如果bean的名称已经存在
	 */
	public static void registerBean(Class<?> cls) {
		registerBean(cls,StringUtils.uncapitalize(cls.getSimpleName()));
	}
	/**
	 * 注册一个新的【单例的】bean到顶级spring容器中
	 * @param cls bean类型
	 * @param name bean名称
	 * 
	 * @throws BeanDefinitionStoreException 如果bean的名称已经存在
	 */
	public static void registerBean(Class<?> cls,String name) {
		registerBean(cls,name,null,null,null);
	}
	
	/**
	 * 注册一个新的bean到顶级spring容器中.<br>
	 * @param cls bean类型
	 * @param name bean名称
	 * @param initMethodName 初始化方法名称，如果为null则不设置，走默认
	 * @param lazy 是否懒加载，如果为null则不设置，走默认
	 * @param scope 生命周期，如果为null则不设置，走默认
	 * 
	 * @throws BeanDefinitionStoreException 如果bean的名称已经存在
	 */
	public static void registerBean(Class<?> cls, String name, String initMethodName, Boolean lazy, String scope) {
		BeanDefinitionBuilder bd = BeanDefinitionBuilder.genericBeanDefinition(cls);
		if (initMethodName != null) {
			bd.setInitMethodName(initMethodName);
		}
		if (lazy != null) {
			bd.setLazyInit(lazy);
		}
		if (scope != null) {
			bd.setScope(scope);
		}
		getBeanFactory().registerBeanDefinition(name, bd.getRawBeanDefinition());
	}
	
	/**
	 * 将一个已经被实例化的对象注册为一个spring bean.
	 * <p>但是需要注意的是，这个bean必须是完全初始化好的，因为注册表不会执行任何回调，例如{@link InitializingBean#afterPropertiesSet()} 初始化回调 和 {@link DisposableBean#destroy()} 销毁回调。
	 * XXX 验证会不会调用销毁回调函数
	 * @param beanName bean名称，不能为null
	 * @param singletonObject 要注册的实例化对象，不能为null
	 */
	public static void registerBean(String beanName,Object singletonObject){
		getBeanFactory().registerSingleton(beanName, singletonObject);
	}
	
	/**
	 * 销毁给定的bean名称的bean。如果找到相应的一次性bean实例，则委托destroyBean。
	 * XXX 验证会不会调用销毁回调函数
	 * @param beanName bean名称
	 */
	public static void destroySingleton(String beanName){
		getBeanFactory().destroySingleton(beanName);
	}
	
	/**
	 * 销毁给定的bean实例.
	 * 
	 * <p>销毁过程中出现的任何异常都应被捕获并记录，而不是传播到该方法的调用方。
	 * @param existingBean 要销毁的bean
	 */
	public static void destroyBean(Object existingBean){
		getBeanFactory().destroyBean(existingBean);
	}

	
	/**
	 * 根据bean名称取得一个bean
	 * @param beanName bean名称
	 * @return 如果存在，则返回bean对象
	 * @throws BeansException bean获取失败则抛出此异常
	 */
	public static Object getBean(String beanName)  {
		return getApplication().getBean(beanName);
	}

	/**
	 * 根据bean名称和bean类型取得一个bean
	 * @param beanName bean名称
	 * @param cls bean类型
	 * @return 如果存在且类型与制定类型一致，则返回此类型对象
	 * @throws BeansException bean获取失败则抛出此异常
	 */
	public static <T> T getBean(String beanName, Class<T> cls)  {
		return getApplication().getBean(beanName, cls);
	}

	/**
	 * 根据bean类型取得一个bean.
	 * 如果有多个同类型的bean，可以通过{@code @}{@link Primary}注解来指定最主要的那个bean
	 * @param cls bean类型
	 * @return 如果存在唯一一个主要类型，则返回，如果有多个同类型的bean，可以通过{@code @}{@link Primary}注解来指定最主要的那个bean
	 * @throws BeansException bean获取失败则抛出此异常
	 */
	public static <T> T getBean(Class<T> cls) {
		return getApplication().getBean(cls);
	}
	
	/**
	 * 取得所有拥有指定注解类型的bean
	 * @param annotationType 注解类型，该注解必须是运行期间可见的
	 * @return 返回所有拥有指定注解的bean集合
	 * @throws BeansException bean实例化异常
	 */
	public static Map<String, Object> getBeansOfAnnotation(Class<? extends Annotation> annotationType) {
		return getApplication().getBeansWithAnnotation(annotationType);
	}
	
	/**
	 * 取得所有继承了该类或实现了该接口的bean
	 * @param typeClass 父类或接口类型
	 * @return 返回所有继承或实现了接口或类的bean的集合。key:bean名称，value:bean对象
	 * @throws BeansException bean实例化异常
	 */
	public static <T> Map<String,T> getBeansOfType(Class<T> typeClass) {
		return getApplication().getBeansOfType(typeClass);
	}
	
	/**
	 * 取得一个资源文件Resource对象.<br>
	 * 
	 * @param resoucePath 资源文件路径。例如：
	 * classpath:xxx, file:/xxx,...
	 * @return 返回Resource对象，Resource不一定存在，你需要调用{@link Resource#exists()}方法来判断资源文件是否存在
	 */
	public static Resource getResource(String resoucePath) {
		return getApplication().getResource(resoucePath);
	}

	/**
	 * 取得一条资源信息，默认安装系统当前语言环境取得.<br>
	 * 
	 * @param key 资源key
	 * @param args [可选]资源参数，会填充到{0},{1},...中
	 * @return 如果存在资源，则返回，否则抛出异常
	 */
	public static String getMessage(String key, String... args) {
		return getMessage(key,Locale.getDefault(),args);
	}
	/**
	 * 取得一条资源信息
	 * 
	 * @param key 资源key
	 * @param locale 要取得的资源的语言环境
	 * @param args [可选]资源参数，会填充到{0},{1},...中
	 * @return 如果存在资源，则返回，否则抛出异常
	 */
	public static String getMessage(String key, Locale locale,String... args) {
		return getApplication().getMessage(key, args, locale);
	}
	/**
	 * 返回与给定键关联的属性值，如果无法解析该键，则返回null。
	 * @param key 要获取的键值
	 */
	public static String getProperty(String key){
		return getEnvironment().getProperty(key);
	}
	
	/**
	 * 返回与给定键关联的属性值，如果无法解析该键，则返回defaultValue。
	 * @param key 要获取的键值
	 * @param defaultValue 无法解析键值是的返回值
	 */
	public static String getProperty(String key,String defaultValue){
		return getEnvironment().getProperty(key,defaultValue);
	}
	
	/**
	 * 返回与给定键关联的属性值，如果无法解析该键，则返回defaultValue。
	 * @param key 要获取的键值
	 * @param targetType 属性值的预期类型
	 */
	public static <T> T getProperty(String key,Class<T> targetType){
		return getEnvironment().getProperty(key,targetType);
	}

	/**
	 * 返回与给定键关联的属性值，如果无法解析该键，则返回defaultValue。
	 * @param key 要获取的键值
	 * @param targetType 属性值的预期类型
	 * @param defaultValue 无法解析键值是的返回值
	 */
	public static <T> T getProperty(String key,Class<T> targetType,T defaultValue){
		return getEnvironment().getProperty(key,targetType,defaultValue);
	}
	
	/**
	 * 将属性源绑定指定的目标类.
	 * <p>这操作如同使用{@link ConfigurationProperties}注解。
	 * @param name 属性源，例如："spring.flyway"
	 * @param target 要绑定的类类型 
	 * @return 如果成功绑定则返回，否则返回null
	 */
	@Nullable
	public static <T> T bind(String name,Class<T> target){
		BindResult<T> bindResult = Binder.get(getEnvironment()).bind(name, target);
		return bindResult.isBound() ? bindResult.get() : null ;
	}
	
	/**
	 * 将属性源绑定指定的目标类.
	 * <p>这操作如同使用{@link ConfigurationProperties}注解。
	 * @param name 属性源，例如："spring.flyway"
	 * @param target 要绑定的类类型 
	 * @return 如果成功绑定则返回，否则仅创建一个实例对象返回，不会返回null
	 */
	public static <T> T bindOrCreate(String name,Class<T> target){
		return Binder.get(getEnvironment()).bindOrCreate(name, target);
	}
	
	/**
	 * 将属性源绑定指定的目标类.
	 * <p>这操作如同使用{@link ConfigurationProperties}注解。
	 * @param name 属性源，例如："spring.flyway"
	 * @param target 要绑定的类类型 
	 * @param other 自定义绑定失败处理操作
	 * @return 如果成功绑定则返回，否则返回other返回的对象
	 */
	public static <T> T bindOrCreate(String name,Class<T> target,Supplier<T> other){
		BindResult<T> bindResult = Binder.get(getEnvironment()).bind(name, target);
		return bindResult.orElseGet(other);
	}
	
	
	/**
	 * 取得当前请求对象，仅当前存在请求时有效
	 * @return spring对 HttpServletRequest 的代理对象，没有返回null
	 */
	@Nullable
	public static HttpServletRequest getRequest() {
		ServletRequestAttributes sr = getServletRequestAttribute();
		return sr == null ? null : sr.getRequest();
	}
	
	/**
	 * 取得当前请求响应对象，仅当前存在请求时有效
	 * @return spring对 HttpServletResponse 的代理对象，没有返回null
	 */
	@Nullable
	public static HttpServletResponse getResponse() {
		ServletRequestAttributes sr = getServletRequestAttribute();
		return sr == null ? null : sr.getResponse();
	}

	/**
	 * 取得ServletRequestAttributes类对象.
	 * <p>不使用注入方式目的是为了能够知道到底目前是否存在request或response，否则必须进行异常捕捉会非常的麻烦。
	 * 
	 * @return 如果当前存在请求，则返回请求对象，否则返回null
	 */
	@Nullable
	private static ServletRequestAttributes getServletRequestAttribute() {
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		if (ra == null) {
			return null;
		}

		if (ra instanceof ServletRequestAttributes) {
			return (ServletRequestAttributes) ra;
		} else {
			throw new IllegalStateException("Current request is not a servlet request");
		}
	}
	
}
